/*1.gcc -o serial serial.cpp -lgpiod
  2.sudo ./serial */
#include <gpiod.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <time.h>
#include <string.h>

#define GPIO_CHIP "/dev/gpiochip3" // 替换为实际的 GPIO 芯片设备文件
#define GPIO_LINE 17 // 替换为实际的 GPIO 引脚编号 为GPIO3_C1 line=2*8+1=17
#define SERIAL_PORT "/dev/ttyS0"  // 替换为实际的串口设备
#define FRAME_HEADER 0xAA           // 帧头
#define FRAME_TAIL 0xBB             // 帧尾
// 获取当前时间（年月日、时分秒）
void get_current_time_str(char* buffer, size_t buffer_size) {//char* buffer：指向字符数组的指针，用于存储格式化后的时间字符串。size_t buffer_size：缓冲区大小，表示字符数组的长度。
    time_t now = time(NULL); //time_t类型它通常用来表示从协调世界时（UTC）1970年1月1日00:00:00（也称为 Unix 纪元）以来经过的秒数
    struct tm* t = localtime(&now);//定义一个指向 tm 结构体的指针 t。将 time_t 类型的时间值 now 转换为本地时间，返回指向 tm 结构体的指针。
    strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S",t);//格式化时间，将 tm 结构体表示的时间转换为指定格式的字符串
}

// 配置串口
int configure_serial(const char* port) {
    int fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);//打开指定的串口设备。O_RDWR 表示以读写模式打开，O_NOCTTY 表示不要将这个设备分配为控制终端，O_SYNC 表示同步输入输出。
    if (fd == -1) { //检查串口设备是否成功打开。如果 open 返回 -1，则表示打开失败。
        perror("Unable to open serial port");
        exit(1);//程序退出
    }

    struct termios tty; //定义一个 termios 结构，用于存储串口配置。
    memset(&tty, 0, sizeof tty); //将 tty 结构清零，确保结构中没有残留的数据。
    if (tcgetattr(fd, &tty) != 0) { //获取当前串口配置并存储在 tty 结构中。如果 tcgetattr 返回非零值，则表示获取失败。
        perror("Error from tcgetattr");
        exit(1);//程序退出
    }

    cfsetospeed(&tty, B115200);//设置输出波特率为 9600。
    cfsetispeed(&tty, B115200);//设置输入波特率为 9600。

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 设置数据位大小为 8 位。
    tty.c_iflag &= ~IGNBRK;         // 禁用 break（中断）处理。
    tty.c_lflag = 0;                // 禁用信号字符、回显和规范处理（行模式
                                 // no canonical processing
    tty.c_oflag = 0;                // 禁用输出映射和延迟。
    tty.c_cc[VMIN]  = 0;            //设置非阻塞读取。
    tty.c_cc[VTIME] = 5;            // 设置读取超时时间为 0.5 秒（5 个十分之一秒）。

    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控制（XON/XOFF）。

    tty.c_cflag |= (CLOCAL | CREAD);// 忽略调制解调器控制线，启用接收功能。
    tty.c_cflag &= ~(PARENB | PARODD);      // 禁用奇偶校验。
    tty.c_cflag |= 0; //保持现有设置（无操作）。 
    tty.c_cflag &= ~CSTOPB;//设置停止位为 1 位。
    tty.c_cflag &= ~CRTSCTS;//禁用硬件流控制（RTS/CTS）。

    if (tcsetattr(fd, TCSANOW, &tty) != 0) { //将配置写回串口。如果 tcsetattr 返回非零值，则表示配置失败。
        perror("Error from tcsetattr"); //如果配置失败，打印错误信息并退出程序。
        exit(1);
    }
    return fd; //返回串口文件描述符。
}

int main() {
    // 初始化 libgpiod
    struct gpiod_chip *chip;//定义并初始化 gpiod_chip 和 gpiod_line 结构体：
    struct gpiod_line *line;
    chip = gpiod_chip_open(GPIO_CHIP); //尝试打开指定的 GPIO 芯片。
    if (!chip) {//检查是否成功打开。如果失败，打印错误信息并退出程序。
        perror("Open chip failed");
        exit(1);
    }
    line = gpiod_chip_get_line(chip, GPIO_LINE);//从打开的 GPIO 芯片中获取指定的 GPIO 行（如行号 17）。GPIO3_C1 则gpiod为（gpiochip3  line=2*8+1=17 ）
    if (!line) {//检查是否成功获取。如果失败，打印错误信息，关闭 GPIO 芯片并退出程序。
        perror("Get line failed");
        gpiod_chip_close(chip);
        exit(1);
    }
    if (gpiod_line_request_output(line, "example", 0) < 0) {//请求将获取到的 GPIO 行配置为输出模式。
        perror("Request line as output failed");//检查请求是否成功。如果失败，打印错误信息，关闭 GPIO 芯片并退出程序。
        gpiod_chip_close(chip);
        exit(1);
    }

    int serial_fd = configure_serial(SERIAL_PORT);
    char buffer[64];
    char send_buffer[128];

    while (1) {
        // 获取当前时间并发送到串口
        get_current_time_str(buffer, sizeof(buffer));//将当前的时间格式化为字符串并存储在 buffer 中。buffer 是一个字符数组，用于存储格式化后的时间字符串。
        // 构造发送数据帧
        int len = snprintf(send_buffer, sizeof(send_buffer), "%c%s%c", FRAME_HEADER, buffer, FRAME_TAIL);
        // 高电平
        gpiod_line_set_value(line, 1); 
        // 发送数据帧
        write(serial_fd, send_buffer, len);// 发送时间字符串
        usleep(100000);  // 高电平持续100毫秒

        // 低电平
        gpiod_line_set_value(line, 0);
        usleep(900000);  // 低电平持续900毫秒，总周期1秒
    }
    close(serial_fd);
    gpiod_chip_close(chip);
    return 0;
}
